Sessions

Sessions overview

The notion of Session is close to DataContext in ADO.NET Entity Framework.

Key facts about sessions:

  • Session is associated with single IDbConnection object that is used to access the underlying storage.
  • Session is created by Domain.OpenSession method. A session always belongs to the domain where it was created, and consequently allows to operate with (and only with) types/services, which are registered in that Domain.
  • Instances of persistent types always belong to (bound to) a session.
  • Session maintains its own cache of fetched entities, which is used and updated completely transparently.
  • Session maintains IdentityMap that provides entity uniqueness.
  • Session tracks changes in its entities and executes required insert, modify & remove operations on Persist.
  • Sessions aren’t safe for the multithreaded operations. To work with a storage in multiple threads you should use separate session for each thread, or manually synchronize access to them.

Session implements IDisposable interface so it is convenient to use it within using construct.

using (var session = domain.OpenSession()) {

  // Work with persistent classes here
}

Session profiles

Starting from DataObjects.Net 4.4 ‘Automatic Session activation’ and ‘Automatic Transaction opening’ modes made optional, moreover, they are switched off by default. In addition, Session Profile notion is introduced. It represents a combination of predefined session options for particular scenarios.

For now there are three main profiles: Server profile, Client profile & Legacy profile.

  • Server profile is intended to be used in server scenarios, such as ASP.NET, ASP.NET MVC, WCF Data Services, etc. It doesn’t include ‘Automatic Session activation’ and ‘Automatic Transaction opening’ modes, so several Session instances could be used within a thread and all actions with Entities, Structures & EntitySets require the presence of opened Transaction. This profile is the default one.
  • Client profile is designed for WPF, Windows Forms, Console applications, whatever. It uses the new ‘Disconnected’ Session mode which means that Session encapsulates an instance of DisconnectedState and runs in occasionally connected conditions, logs modifications of Entities and applies them to a database on request. It doesn’t include ‘Automatic Session activation’ either but has ‘Automatic Transaction opening’ option switched on (in this mode it uses local in-memory transactions, not database ones, in order to log Entity modifications).
  • To preserve compatibility with Session & Transaction behavior from previous version of DataObjects.Net a special profile named Legacy is added, which has both ‘Automatic Session activation’ and ‘Automatic Transaction opening’ modes switched on.

Session profiles usage

Session profile is a combination of SessionOptions values, so we build SessionConfiguration instance using this profile and construct Session afterwards.

var configuration = new SessionConfiguration(
    "mySession", SessionOptions.ClientProfile);
using (var session = domain.OpenSession(configuration)) {
   // some code
}

In case you want every Session within your Domain runs in a particular profile by default, you can set up Default Session in applicatin configuration file:

<domain upgradeMode="..." connectionUrl="...">
  <types>
    <add assembly="..."/>
  </types>
  <sessions>
    <session name="Default" options="ClientProfile" />
  </sessions>
</domain>

You may achieve the same by setting up Default Session in C# code:

var domainConfiguration = new DomainConfiguration();
var sessionConfiguration = new SessionConfiguration(
    "Default", SessionOptions.ClientProfile);
domainConfiguration.Sessions.Add(sessionConfiguration);
var domain = Domain.Build(domainConfiguration);

Configuring sessions

Another method overload domain.OpenSession(sessionConfig) accepts SessionConfiguration instance, that contains several session-level options:

  • CacheSize – size of the entity cache, default value is 16384 entities.
  • CacheTypeLruWeak (default value) or Infinite.
  • DefaultIsolationLevel for transactions, isolation level can be also specified when a particular transaction is being opened.
  • BatchSize – maximal size of SQL statement batches, this affects create, update, delete operations and future queries.
  • Options – session options flag.

Session configuration can be created manually:

var sessionCongfig = new SessionConfiguration {
  BatchSize = 25,
  DefaultIsolationLevel = IsolationLevel.ReadCommitted,
  CacheSize = 1000,
  Options = SessionOptions.ServerProfile
};

Or declared and loaded from configuration file:

<domain name="TestDomain"
        upgradeMode="Recreate"
        connectionUrl="sqlserver://localhost/MyTests" >
  <sessions>
    <session name="TestSession"
             batchSize="25" isolationLevel="ReadCommitted"
             cacheSize="1000" options="AutoShortenTransactions" />
  </sessions>
</domain>
var domainConfig = DomainConfiguration.Load("TestDomain");
var sessionConfig = domainConfig.Sessions["TestSession"];

When session configuration is created or loaded, session with this configuration can be opened by special Open() method overload:

using (var session = domain.OpenSession(sessionConfig)) {
  // ...
}

Current session, session activation

Note

This chapter describes session particularities in DataObjects.Net 4.3 and DataObjects.Net 4.4 with SessionProfile=Legacy.

Session class implements IContext interface, it means that a session can be either active or not active in particular thread. Each thread can contain only one active session, that can be a accessed via Session.Current property or Session.Demand() method. When you creates new entitiy or loads it from database, they are automatically bounded to the current session.

New session can be opened and activated by domain.OpenSession() method. In the following example newly opened session is automatically activated within the using block:

using (var session = domain.OpenSession()) {

  // creating new entity
  var newPerson = new Person();

  // fetching entity by its ID
  var fetchedPerson = session.Query.Single<Person>(personId);

  Console.WriteLine("Our session is current: {0}",
    Session.Current==session);

  Console.WriteLine("New entity is bound to our session: {0}",
    newPerson.Session==session);

  Console.WriteLine("Fetched entity is bound to our session: {0}",
    fetchedPerson.Session==session);
}

The result will be true in all cases.

Existing session can be activated by Activate() method:

using (session.Activate()) {

  // Work with persistent classes here
}

DataObjects.Net provides the following session activation pattern:

  • Session.Activate binds a session to the current thread.
  • Session.Deactivate unbinds currently active session from the current thread.
  • Active (or current) session is session that was bound to the current thread most recently, but not yet deactivated.
  • All above implies each thread maintains its own stack of activated sessions. When activation happens on a particular thread, a new session is added to the top of its stack; this session becomes active (current) in this thread. When a particular session is deactivated, the session below it on the stack becomes active (current). If deactivated session is not the topmost session on the stack, all the above sessions are deactivated implicitely.

Session activation API:

  • Session.Current returns active (current) Session bound to the current Thread; if there is no active session, it returns null.
  • Session.Demand() returns active session, if it exists. If there is no active session, it throws InvalidOperationException.
  • Session.Activate() activates the session. This method returns IDisposable object that must be disposed to deactivate the session. So normally Session.Activate() must be used inside using block.

That’s true:

  • Moreover, by default any public method (or property accessor) of any SessionBound descendant is decorated by aspect activating its SessionBound.Session for the duration of execution of this method.
    • [Transactional(ActivateSession = false)] attribute prevents automatic session activation for public SessionBound methods;
    • [Transactional(ActivateSession = true)] enforces automatic session activation for any (e.g. non-public) SessionBound methods.

So since all the objects requiring a Session (e.g. Entity, Structure and EntitySet<T>) are SessionBound descendants, almost all of their public methods implicitly activate the Session they’re bound to. This ensures:

  • You can use Session.Demand() and Session.Current inside their code, or inside any code they invoke.

  • So e.g. you can invoke static methods without passing the Session to them directly.

    Note

    Is the session activation technique fast? For example, Session.Activate() always returns an IDisposable object – so does this mean any SessionBound method call allocates something on heap?

No, it doesn’t: actually, Session.Activate() returns a special IDisposable singleton (so-called void scope) doing nothing on its disposal, if this Session is already active. So this is pretty fast process.

Current Domain

Presence of current Session allows us to provide current Domain as well:

  • Domain.Current is actually based on Session.Current;
  • The same is true for Domain.Demand().

Here is actual Domain.Current property code:

public static Domain Current {
  get {
    var session = Session.Current;
    return session!=null ? session.Domain : null;
  }
}

Session Events

Session type exposes wide range of events, from Entity-related events to DbCommand events. Session events are accessed through Session.Events property. Unlike ‘event-like’ protected virtual methods in Entity & EntitySet types, session events are preferred in case you don’t have access to source code of the domain model or going to be less invasive and more flexible.

Misc events

  • EventHandler<DbCommandEventArgs> DbCommandExecuting
    Occurs when DbCommand is about to execute.
  • EventHandler<DbCommandEventArgs> DbCommandExecuted
    Occurs when DbCommand is executed.
  • EventHandler<KeyEventArgs> KeyGenerated
    Occurs when local Key created.